#!/bin/sh

## TODO: handle srt encoding?

usage () {
	cat <<EOF>&2
Usage: ${0##*/} [OPTIONS] FILES|FOLDERS

Transcode FILES or files found in FOLDERS to x264 and ogg vorbis in a MKV container.
Black stripes are *not* cropped by default since it may be sometimes inaccurate.
A time stamp is appended to the output filenames.

CRF encoding is performed by default. It targets a desired quality. CRF 20 is
usually a good factor, 18 is higher quality, 22 is lower. The 18-22 range is
reasonable. The quality loss is usually noticeable above 22 and the size
increase is significant below 18 for a barely noticeable quality change.

2-pass encoding can achieve best compression, if the given bitrate is
appropriate. A sample in CRF encoding can give a good bitrate estimate for the
desired quality. Bitrates for 1080p movies are found within the 1000k-10000k
range.

You can get a list of supported codecs with

  $ ffmpeg -codecs

User options are read from the TC_VIDEO_OPT variable. This can be useful if you
often use the same options.

Options:
  -a CODEC: Audio codec supported by ffmpeg. (Default: libvorbis).
  -b:       Default bitrate for audio stream with unidentifiable bitrate.
            If 0, copy stream (default).
  -c:       Copy streams (no reencoding).
  -C:       Enable auto-crop (needs video reencoding).
  -d BR:    Perform a 2-pass video encoding at bitrate BR and set '-tune film'.
  -f:       Remove source when done.
  -h:       Display this help.
  -o OPT:   Additional options.
  -p:       Preview changes, do not encode.
  -P:       Generate two sequence of thumbnails, one cropped, the other not.
            Enable auto-crop, do not encode.
  -q QUAL:  x264 CRF quality (default 20).
  -s:       Sample of 5 minutes.
  -S MIN:   Sample of MIN minutes.
  -t:       Remove all "title" metadata.

Examples:

* Get a preview of changes.

  ${0##*/} -p input.video

* Proceed with default options over folders and files.

  ${0##*/} input-folder input.video

* Preview black stripes cropping.

  ${0##*/} -P input.video

* Process with default options and remove black stripes.

  ${0##*/} -C input.video

* Exchange streams 1 and 2 by first removing them, then adding them in the
  desired order.

  ${0##*/} -o '-map -0:1 -map -0:2 -map 0:2 -map 0:1' input.video

* Change audio stream 1 title and remove audio stream 2 title:

  ${0##*/} -o '-metadata:s:a:0 title="FR: OGG Stereo" -metadata:s:a:1 title=' input.video

See <https://trac.ffmpeg.org/wiki/Encode/H.264>, 'ffmpeg -h encoder=libx264' and 'x264 --fullhelp'.
EOF
}

EXT="mkv"
AUDIO_CODEC="libvorbis"
VIDEO_PARAM="-c:v libx264 -preset slower -crf 20"
AUDIO_PARAM=""

VIDEO_FILTER=""
AUDIO_DEFAULT_RATE=0

OVERWRITE="-n"
SAMPLE=""
OPT_OVERWRITE=false
OPT_REMOVE_TITLE=false
OPT_CROP=false
OPT_CROPPREVIEW=false
OPT_PREVIEW=false
OPT_COPY=false
OPT_2PASS=false

while getopts ":a:b:cCd:fho:pPq:sS:t" opt; do
	case $opt in
	a)
		AUDIO_CODEC=$OPTARG ;;
	b)
		AUDIO_DEFAULT_RATE=$OPTARG ;;
	c)
		VIDEO_PARAM="-c:v copy"
		AUDIO_PARAM="-c:a copy"
		OPT_COPY=true;;
	C)
		OPT_CROP=true;;
	d)
		VIDEO_PARAM="-c:v libx264 -preset slower -b:v ${OPTARG}k"
		OPT_2PASS=true;;
	f)
		OVERWRITE="-y"
		OPT_OVERWRITE=true;;
	h)
		usage
		exit 1 ;;
	o)
		TC_VIDEO_OPT="$OPTARG" ;;
	p)
		OPT_PREVIEW=true ;;
	P)
		OPT_CROP=true
		OPT_PREVIEW=true
		OPT_CROPPREVIEW=true ;;
	q)
		VIDEO_PARAM="-c:v libx264 -preset slower -crf $OPTARG";;
	s)
		SAMPLE="-ss 60 -t 300" ;;
	S)
		SAMPLE="-ss 60 -t $((60*OPTARG))" ;;
	t)
		OPT_REMOVE_TITLE=true ;;
	\?)
		usage
		exit 1 ;;
	esac
done

shift $((OPTIND - 1))
if [ $# -eq 0 ]; then
	usage
	exit
fi

if ! command -v ffmpeg >/dev/null; then
	echo "ffmpeg required."
	exit
fi

highfreq () {
	awk 'BEGIN{max=0} /crop=/ {t[$NF]++; if (t[$NF]>max) {max=t[$NF]; val=$NF}} END{print val}'
}

## Usage: cropvalue FILE STEP
## Return the crop values as ffmpeg output them.
cropvalue () {
	local step=$2
	for i in $(seq "$step" "$step" $((5*step))); do
		ffmpeg -nostdin -ss "$i" -i "$1"  -t 10 -vf "cropdetect=24:2:0" -f null - 2>&1
	done | highfreq
}

## Return the audio encoding parameters. For instance
##   -c:2 libvorbis -b:2 384k -c:5 copy
## If input codec is $AUDIO_CODEC, we copy. If some stream bitrates are missing
## or 0, we encode it to a default value. If default value is 0, then we copy
## stream.
audiobitrate () {
	local bitrate

	for i in $(seq 0 $((format_nb_streams-1))); do
		## Skip non audio tracks.
		[ "$(eval echo \$streams_stream_"$i"_codec_type)" != "audio" ] && continue

		bitrate=$(eval echo \$streams_stream_"$i"_bit_rate)
		if [ -n "$bitrate" ] && [ "$bitrate" -gt 0 ] 2>/dev/null; then
			## If non-empty and a positive number.
			bitrate=$((bitrate / 1000))
		else
			bitrate="$AUDIO_DEFAULT_RATE"
		fi
		if [ "$bitrate" -le 0 ] || \
				 [ "$AUDIO_CODEC" = "$(eval echo \$streams_stream_"$i"_codec_name)" ] || \
				 [ "$AUDIO_CODEC" = "lib$(eval echo \$streams_stream_"$i"_codec_name)" ]; then
			printf -- "-c:%s copy " "$i"
		else
			[ "$bitrate" -gt 500 ] && bitrate=500
			printf -- "-c:%s %s -b:%s %sk " "$i" "$AUDIO_CODEC" "$i" "$bitrate"
		fi
	done
}

transcode () {
	echo "==> [$1]"
	OUTPUT="${1%.*}.$EXT"
	[ -e "$OUTPUT" ] && OUTPUT="${1%.*}-$(date '+%F-%H%M%S').$EXT"

	## Metadata (i.e. tags + technical data).
	buffer="$(ffprobe -v quiet -print_format flat=s=_ -show_streams -show_format "$1")"
	if [ $? -ne 0 ]; then
		echo "File [$1] is unsupported by FFmpeg."
		return
	fi
	## The following 'eval' defines variables such as format_nb_streams
	eval "$buffer"
	unset buffer

	STREAM_TITLE=""
	if $OPT_REMOVE_TITLE; then
		for i in $(seq 0 $((format_nb_streams-1)) ); do
			STREAM_TITLE="${STREAM_TITLE}-metadata:s:$i title= "
		done
	fi

	if $OPT_CROP; then
		echo "Computing crop values... "
		## For 5 different timeslices of 1 second at every 1/6th of the video,
		## we sample the crop values. We keep the values with highest
		## frequency. This is much faster than encoding in one pass with low
		## framerate.
		STEP=${format_duration:-$streams_stream_0_duration}
		STEP="${STEP%%.*}"
		STEP=$((STEP/6))
		VIDEO_FILTER="-vf $(cropvalue "$1" "$STEP")"
		if $OPT_CROPPREVIEW; then
			echo "Generating preview... "
			for i in $(seq  $STEP $STEP $((5*STEP))); do
				ffmpeg -nostdin -v warning -y -ss "$i" -i "$1" \
					-f image2 -vframes 1 "$VIDEO_FILTER" "${1%.*}-preview-$i-cropped.png" \
					-f image2 -vframes 1 "${1%.*}-preview-$i-uncropped.png"
			done
		fi
	fi

	## WARNING: We mix down audio to 2 channels with '-ac 2'. This greatly reduce
	## file size and avoid any confusion for playback, which is often the case
	## when converting DTS to any other format. (DTS has embedded channel
	## description which is not always available in other formats.)
	! $OPT_COPY && AUDIO_PARAM="$(audiobitrate "$1") -ac 2"

	cat <<EOF>&2
User options:  ${TC_VIDEO_OPT:-none}
Sample:        ${SAMPLE:-no}
Clear tags:    $OPT_REMOVE_TITLE
In place:      $OPT_OVERWRITE
Crop:          $OPT_CROP
Video params:  $VIDEO_PARAM
Video filter:  $VIDEO_FILTER
Audio params:  $AUDIO_PARAM
Output file:   $OUTPUT

EOF

	$OPT_PREVIEW && return

	if $OPT_2PASS; then
		echo ":: FIRST PASS"
		ffmpeg -nostdin -y "$SAMPLE" -i "$1" \
			"$VIDEO_PARAM" -pass 1 -tune film "$VIDEO_FILTER" \
			"$AUDIO_PARAM" \
			-c:s copy \
			-dn \
			-map 0 "$STREAM_TITLE" \
			"$TC_VIDEO_OPT" -f matroska /dev/null
		VIDEO_PARAM="$VIDEO_PARAM -pass 2 -tune film"
		echo
		echo ":: SECOND PASS"
	fi

	ffmpeg -nostdin "$OVERWRITE" -i "$1" \
		"$VIDEO_PARAM" "$VIDEO_FILTER" \
		"$AUDIO_PARAM" \
		-c:s copy \
		-dn \
		-map 0 "$STREAM_TITLE" \
		"$SAMPLE" "$TC_VIDEO_OPT" "$OUTPUT"

	$OPT_2PASS && rm -v ffmpeg2pass-0.log ffmpeg2pass-0.log.mbtree

	if $OPT_OVERWRITE; then
		rm "$1"
		mv -f "$OUTPUT" "${1%.*}.$EXT"
	fi
	echo
}

for i; do
	transcode "$i"
done
